Lab 5 - Data Visualization

COMP7270 - Web and Mobile Programming - HKBU - Spring2025

In this lab, you will learn how to create dynamic data visualizations using the amCharts library in combination with a database. Data visualization is a powerful tool for presenting and understanding large sets of data in a visual format. With amCharts, you can create interactive and visually appealing charts, graphs, and maps.

To get started, download the zip file from Moodle and create a new file called amCharts.html in the public folder. Use the following starter code:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>amCharts</title>
</head>

<body>
    <script src="https://cdn.amcharts.com/lib/5/index.js"></script>
    <script src="https://cdn.amcharts.com/lib/5/map.js"></script>
    <script src="https://cdn.amcharts.com/lib/5/percent.js"></script>
    <script src="https://cdn.amcharts.com/lib/5/themes/Animated.js"></script>
    <script src="https://cdn.amcharts.com/lib/5/geodata/worldLow.js"></script>
    <script src="https://cdn.amcharts.com/lib/5/geodata/hongKongHigh.js"></script>

    <div id="chartdiv_map" style="width:100vw; height:100vh"></div>
    <div id="chartdiv_hk" style="width:100vw; height:100vh; background-color:AliceBlue"></div>
    <div id="chartdiv_plant" style="width:50vw; height:80vh"></div>
    <div id="chartdiv_snsd" style="width:90vw; height:100vh"></div>

    <script src="javascripts/svgPaths.js"></script>

    <script src="javascripts/map.js"></script>
    <script src="javascripts/hkMap.js"></script>
    <script src="javascripts/plant.js"></script>
    <script src="javascripts/snsd.js"></script>
</body>

</html>
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>amCharts</title>
</head>

<body>
    <script src="https://cdn.amcharts.com/lib/5/index.js"></script>
    <script src="https://cdn.amcharts.com/lib/5/map.js"></script>
    <script src="https://cdn.amcharts.com/lib/5/percent.js"></script>
    <script src="https://cdn.amcharts.com/lib/5/themes/Animated.js"></script>
    <script src="https://cdn.amcharts.com/lib/5/geodata/worldLow.js"></script>
    <script src="https://cdn.amcharts.com/lib/5/geodata/hongKongHigh.js"></script>

    <div id="chartdiv_map" style="width:100vw; height:100vh"></div>
    <div id="chartdiv_hk" style="width:100vw; height:100vh; background-color:AliceBlue"></div>
    <div id="chartdiv_plant" style="width:50vw; height:80vh"></div>
    <div id="chartdiv_snsd" style="width:90vw; height:100vh"></div>

    <script src="javascripts/svgPaths.js"></script>

    <script src="javascripts/map.js"></script>
    <script src="javascripts/hkMap.js"></script>
    <script src="javascripts/plant.js"></script>
    <script src="javascripts/snsd.js"></script>
</body>

</html>

In this code, index.js provides the core functionality of amCharts and introduces the am5 object.

The map.js file provides the am5map object, which is necessary for drawing maps. The worldLow.js file supplies the world map in a low polygon format. If you need a map with more details, you can choose from worldHigh.js and worldUltra.js. We will revisit the other JavaScript files later.

To create charting areas, we have included several div HTML elements with unique ids. These elements will serve as the containers for our charts.

JavaScript

Next, inside the /public/javascripts folder, create a new file map.js with the following implementation:

am5.ready(function () {
    // Create root and chart
    var root = am5.Root.new("chartdiv_map");
    var chart = root.container.children.push(
        am5map.MapChart.new(root, { wheelY: "none" })
    );

    var polygonSeries = chart.series.push(
        am5map.MapPolygonSeries.new(root, {
            geoJSON: am5geodata_worldLow,
        })
    ); 
});
am5.ready(function () {
    // Create root and chart
    var root = am5.Root.new("chartdiv_map");
    var chart = root.container.children.push(
        am5map.MapChart.new(root, { wheelY: "none" })
    );

    var polygonSeries = chart.series.push(
        am5map.MapPolygonSeries.new(root, {
            geoJSON: am5geodata_worldLow,
        })
    ); 
});

Every amChart should start with a root, which can develop multiple charts if needed. A root is created with am5.Root.new(), and expected an argument which specifies the id of the div element for this chart.

We then create a map chart instance (chart), which expects a MapPolygonSeries to be provided. A MapPolygonSeries is usually in GeoJSON format. This data is provided from the https://cdn.amcharts.com/lib/5/geodata/worldLow.js, the JavaScript we imported previously.

Screenshot 2022-07-19 at 3.25.13 PM.png

Antarctica

Under the current projection, Antarctica is quite big and we'd like to remove it from the display. To do so, we need to know the id of this polygon.

In https://cdn.amcharts.com/lib/5/geodata/worldLow.js, search for Antarctica and look for its id, which turns out to be AQ.

Put the following line below the geoJSON line to exclude this polygon.

exclude: ["AQ"],
exclude: ["AQ"],

Or, if you want to focus on certain countries, you can use include as well:

include: ["AR", "BR"],
include: ["AR", "BR"],

Tooltips

Let say we want to show the world cup rivalry between Argentina and Brazil, we can first develop a dataset to the polygon series.

polygonSeries.data.setAll([{
    id: "AR",
    worldcup: 2
}, {
    id: "BR",
    worldcup: 5
}])
polygonSeries.data.setAll([{
    id: "AR",
    worldcup: 2
}, {
    id: "BR",
    worldcup: 5
}])

Then, the following will set up a tooltip:

polygonSeries.mapPolygons.template.setAll({
    tooltipText: "{name}: {worldcup} times",
});
polygonSeries.mapPolygons.template.setAll({
    tooltipText: "{name}: {worldcup} times",
});

Here, the {name} field is given in the GeoJSON data, while the {worldcup} field is a custom-defined attribute.

Color

Apart from tooltip, the mapPolygons.template can also define the color of the polygons. Modify the above code segment as follows:

polygonSeries.mapPolygons.template.setAll({
    tooltipText: "{name}: {worldcup} times",
    templateField: "polygonSettings"
});
polygonSeries.mapPolygons.template.setAll({
    tooltipText: "{name}: {worldcup} times",
    templateField: "polygonSettings"
});

The polygonSettings could then be defined in the data:

polygonSeries.data.setAll([{
    id: "AR",
    worldcup: 2,
    polygonSettings: {
        fill: am5.color(0x43A1D5)
    }
}, {
    id: "BR",
    worldcup: 5,
    polygonSettings: {
        fill: am5.color(0xFFDC02)
    }
}]);
polygonSeries.data.setAll([{
    id: "AR",
    worldcup: 2,
    polygonSettings: {
        fill: am5.color(0x43A1D5)
    }
}, {
    id: "BR",
    worldcup: 5,
    polygonSettings: {
        fill: am5.color(0xFFDC02)
    }
}]);

The 18 Districts in Hong Kong

To develop a Hong Kong map, we have imported the following script tag.

<script src="https://cdn.amcharts.com/lib/5/geodata/hongKongHigh.js"></script>
<script src="https://cdn.amcharts.com/lib/5/geodata/hongKongHigh.js"></script>

Next, create a new file hkmap.js inside the javascripts folder, and implement the following:

am5.ready(function () {

    // Create root and chart
    var root = am5.Root.new("chartdiv_hk");
    var chart = root.container.children.push(
        am5map.MapChart.new(root, { wheelY: "none" })
    );

    var polygonSeries = chart.series.push(
        am5map.MapPolygonSeries.new(root, {
            geoJSON: am5geodata_hongKongHigh,
        })
    );
})
am5.ready(function () {

    // Create root and chart
    var root = am5.Root.new("chartdiv_hk");
    var chart = root.container.children.push(
        am5map.MapChart.new(root, { wheelY: "none" })
    );

    var polygonSeries = chart.series.push(
        am5map.MapPolygonSeries.new(root, {
            geoJSON: am5geodata_hongKongHigh,
        })
    );
})

A map of Hong Kong in GeoJSON format could be found on https://cdn.amcharts.com/lib/5/geodata/hongKongHigh.js, and we also specified the use of this map with am5geodata_hongKongHigh.

A research report on the children injury in Hong Kong is available on this link. Page 23 discusses the child abuse cases in the 18 Hong Kong districts.

Let's develop the title as follows:

// Title
var title = chart.children.push(am5.Label.new(root, {
    text: "Abuse cases A&E attendance by District\n(per 100,000 population)",
    fontSize: 20,
    y: 20,
    x: am5.percent(20),
    centerX: am5.p50,
    background: am5.Rectangle.new(root, {
        fill: am5.color(0xffffff),
        fillOpacity: 0.5
    })
}));
// Title
var title = chart.children.push(am5.Label.new(root, {
    text: "Abuse cases A&E attendance by District\n(per 100,000 population)",
    fontSize: 20,
    y: 20,
    x: am5.percent(20),
    centerX: am5.p50,
    background: am5.Rectangle.new(root, {
        fill: am5.color(0xffffff),
        fillOpacity: 0.5
    })
}));

Screenshot 2022-11-13 at 12.06.22 PM.png

The data on page 23 is summarized in the following object array.

polygonSeries.data.setAll([
    { id: "HK-YT", value: 17.1 },
    { id: "HK-YL", value: 42.6 },
    { id: "HK-WT", value: 20.1 },
    { id: "HK-WC", value: 18.7 },
    { id: "HK-TW", value: 14.9 },
    { id: "HK-TP", value: 16.3 },
    { id: "HK-TM", value: 38.3 },
    { id: "HK-ST", value: 14.1 },
    { id: "HK-SS", value: 21.3 },
    { id: "HK-SO", value: 22.0 },
    { id: "HK-SK", value: 17.8 },
    { id: "HK-NO", value: 17.6 },
    { id: "HK-KU", value: 15.9 },
    { id: "HK-KI", value: 23.3 },
    { id: "HK-KC", value: 17.5 },
    { id: "HK-IS", value: 21.0 },
    { id: "HK-EA", value: 18.2 },
    { id: "HK-CW", value: 14.5 }
]);
polygonSeries.data.setAll([
    { id: "HK-YT", value: 17.1 },
    { id: "HK-YL", value: 42.6 },
    { id: "HK-WT", value: 20.1 },
    { id: "HK-WC", value: 18.7 },
    { id: "HK-TW", value: 14.9 },
    { id: "HK-TP", value: 16.3 },
    { id: "HK-TM", value: 38.3 },
    { id: "HK-ST", value: 14.1 },
    { id: "HK-SS", value: 21.3 },
    { id: "HK-SO", value: 22.0 },
    { id: "HK-SK", value: 17.8 },
    { id: "HK-NO", value: 17.6 },
    { id: "HK-KU", value: 15.9 },
    { id: "HK-KI", value: 23.3 },
    { id: "HK-KC", value: 17.5 },
    { id: "HK-IS", value: 21.0 },
    { id: "HK-EA", value: 18.2 },
    { id: "HK-CW", value: 14.5 }
]);

The ids come from the hongKongHigh.js file, and the value property gives us the number of abuses cases in a 100,000 population.

Then, develop a tooltip to show this information:

polygonSeries.mapPolygons.template.setAll({
    tooltipText: "{name}: {value} cases"
});
polygonSeries.mapPolygons.template.setAll({
    tooltipText: "{name}: {value} cases"
});

Heat Rules

We want to color the districts based on the number of abuse cases. This can be done by setting a heatRules in the series. Heat rules is a way to apply value-dependent setting values on series elements.

polygonSeries.set("heatRules", [{
    target: polygonSeries.mapPolygons.template,
    dataField: "value",
    min: am5.color(0xdedede),
    max: am5.color(0x880000),
    key: "fill"
}]);
polygonSeries.set("heatRules", [{
    target: polygonSeries.mapPolygons.template,
    dataField: "value",
    min: am5.color(0xdedede),
    max: am5.color(0x880000),
    key: "fill"
}]);

In order for heat rules to function properly on series, it needs aggregate values (high and low) to be calculated for the series. Thus, let's add valueField and calculateAggregates in the polygon series (do not create a new polygonSeries object, but add two attributes to the object):

var polygonSeries = chart.series.push(
    am5map.MapPolygonSeries.new(root, {
        geoJSON: am5geodata_hongKongHigh,
        valueField: "value",
        calculateAggregates: true
    })
);
var polygonSeries = chart.series.push(
    am5map.MapPolygonSeries.new(root, {
        geoJSON: am5geodata_hongKongHigh,
        valueField: "value",
        calculateAggregates: true
    })
);

Interaction

Finally, we also set up a color for the hovered district:

polygonSeries.mapPolygons.template.states.create("hover", {
    fill: root.interfaceColors.get("primaryButtonHover")
});
polygonSeries.mapPolygons.template.states.create("hover", {
    fill: root.interfaceColors.get("primaryButtonHover")
});

Screenshot 2022-11-13 at 12.01.05 PM.png

Pictorial (Sliced Chart)

This type of infographic is particularly popular on social media. You can find a working example from https://www.amcharts.com/demos/pictorial-fraction-chart/.

To develop a sliced chart, we need the following scripts:

<script src="https://cdn.amcharts.com/lib/5/percent.js"></script>
<script src="https://cdn.amcharts.com/lib/5/percent.js"></script>

Next, create a file plant.js inside folder javascripts and develop the following:

am5.ready(function () {

    var root = am5.Root.new("chartdiv_plant");
    var chart = root.container.children.push(
        am5percent.SlicedChart.new(root, {})
    );

    var series = chart.series.push(
        am5percent.PictorialStackedSeries.new(root, {
            svgPath: plantPath,
            categoryField: "name",
            valueField: "value",
        })
    );

    series.slices.template.setAll({
        tooltipText: "{name}: {value}%"
    });

    series.data.setAll([{
        name: "Dry",
        value: 33.2
    }, {
        name: "Water",
        value: 66.8
    }]);
});
am5.ready(function () {

    var root = am5.Root.new("chartdiv_plant");
    var chart = root.container.children.push(
        am5percent.SlicedChart.new(root, {})
    );

    var series = chart.series.push(
        am5percent.PictorialStackedSeries.new(root, {
            svgPath: plantPath,
            categoryField: "name",
            valueField: "value",
        })
    );

    series.slices.template.setAll({
        tooltipText: "{name}: {value}%"
    });

    series.data.setAll([{
        name: "Dry",
        value: 33.2
    }, {
        name: "Water",
        value: 66.8
    }]);
});

We have to tell that the graphics has to be sliced according to the value attribute, and this could be done by specifying the valueField.

The svgPath property expects an SVG path to be provided. There are many vector graphics on the web, some are in the public domain. Here's one of them: https://www.svgrepo.com/svg/208677/plant-garden

Download this SVG file and open it with Visual Studio Code. Locate the first path element. Copy the value of the d attribute and assign it to the constant plantPath in plant.js as follows:

var plantPath = `M412.457,314.914H251.863c1.862-3.609,3.995-7.984,6.24-13.026c0.765-0.389
var plantPath = `M412.457,314.914H251.863c1.862-3.609,3.995-7.984,6.24-13.026c0.765-0.389

Please note that the backticks develop multiline text string. Note that we should put plantPath at the beginning of plant.js.

Setting colors for the sliced chart is a lot easier, simply adding the following statement can do the trick. Note that the color setting should be put before series.data.setAll().

series.get("colors").set("colors", [
    am5.color(0xe0a458),
    am5.color(0x80c5de),
]);
series.get("colors").set("colors", [
    am5.color(0xe0a458),
    am5.color(0x80c5de),
]);

Custom Image

Create a new file snsd.js in the javascripts folder and develop the following:

am5.ready(function () {

    var root = am5.Root.new("chartdiv_snsd");
    var chart = root.container.children.push(
        am5percent.SlicedChart.new(root, {})
    );

    var series = chart.series.push(
        am5percent.PictorialStackedSeries.new(root, {
            svgPath: snsdPath,
            categoryField: "name",
            valueField: "value",
            orientation: "horizontal"
        })
    );

    series.slices.template.setAll({
        tooltipText: "{name} Entertainment: {value} billions KRW."
    });

    series.get("colors").set("colors", [
        am5.color(0xff4980),
        am5.color(0xe9cdc2),
        am5.color(0x773d31),
    ]);

    series.data.setAll([{
        name: "SM",
        value: 365387
    }, {
        name: "JYP",
        value: 102242
    },{
        name: "YG",
        value: 349861
    } ]);
});
am5.ready(function () {

    var root = am5.Root.new("chartdiv_snsd");
    var chart = root.container.children.push(
        am5percent.SlicedChart.new(root, {})
    );

    var series = chart.series.push(
        am5percent.PictorialStackedSeries.new(root, {
            svgPath: snsdPath,
            categoryField: "name",
            valueField: "value",
            orientation: "horizontal"
        })
    );

    series.slices.template.setAll({
        tooltipText: "{name} Entertainment: {value} billions KRW."
    });

    series.get("colors").set("colors", [
        am5.color(0xff4980),
        am5.color(0xe9cdc2),
        am5.color(0x773d31),
    ]);

    series.data.setAll([{
        name: "SM",
        value: 365387
    }, {
        name: "JYP",
        value: 102242
    },{
        name: "YG",
        value: 349861
    } ]);
});

Here, to develop a horizontal pictorial, we change the orientation of the PictorialStackedSeries to horizontal.

Next, let's create the file javascripts/svgPaths.js with the following content:

const snsdPath = ``
const snsdPath = ``

To develop a pictorial using our own graphics, we can consider converting an image file into SVG. Most vector-based graphics applications support this option and there are also some online alternatives, like picsvg.com or pngtosvg.com

Let's download this file:

In pngtosvg.com, select 3 colors and set simplify to 1.

Download the SVG file and open it with Visual Studio Code. In the downloaded SVG file, locate the first path element, and copy the value of the d attribute. Assign it to the constant snsdPath.

Develop the following to rotate labels:

series.labels.template.set("rotation", 0);
series.labels.template.set("rotation", 0);

Format the labels alignment lines with

series.ticks.template.set("location", 0.95);
series.ticks.template.set("location", 0.95);

Finally, we can adjust the vertical position of the tooltips with

series.slices.template.set("tooltipY", 75);
series.slices.template.set("tooltipY", 75);

Screenshot 2022-11-14 at 12.48.03 AM.png

Data Visualization with Dynamic Data

To enable data visualization with dynamic data in amCharts, we will create a new EJS file called snsd.ejs. Let's add the following code to the file:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>amCharts</title>
</head>

<body>
    <script src="https://cdn.amcharts.com/lib/5/index.js"></script>
    <script src="https://cdn.amcharts.com/lib/5/percent.js"></script>
    <script src="https://cdn.amcharts.com/lib/5/themes/Animated.js"></script>

    <div id="chartdiv_snsd" style="width:90vw; height:100vh"></div>

    <script src="javascripts/svgPaths.js"></script>
    <script>
        am5.ready(function () {

            var root = am5.Root.new("chartdiv_snsd");
            var chart = root.container.children.push(
                am5percent.SlicedChart.new(root, {})
            );

            var series = chart.series.push(
                am5percent.PictorialStackedSeries.new(root, {
                    svgPath: snsdPath,
                    categoryField: "name",
                    valueField: "value",
                    orientation: "horizontal"
                })
            );

            series.slices.template.setAll({
                tooltipText: "{name} Entertainment: {value} billions KRW."
            });

            series.get("colors").set("colors", [
                am5.color(0xff4980),
                am5.color(0xe9cdc2),
                am5.color(0x773d31),
            ]);

            series.labels.template.set("rotation", 0);
            series.ticks.template.set("location", 0.95);
            series.slices.template.set("tooltipY", 75);

            series.data.setAll([{
                name: "SM",
                value: 365387
            }, {
                name: "JYP",
                value: 102242
            }, {
                name: "YG",
                value: 349861
            }]);
        });
    </script>
</body>

</html>
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>amCharts</title>
</head>

<body>
    <script src="https://cdn.amcharts.com/lib/5/index.js"></script>
    <script src="https://cdn.amcharts.com/lib/5/percent.js"></script>
    <script src="https://cdn.amcharts.com/lib/5/themes/Animated.js"></script>

    <div id="chartdiv_snsd" style="width:90vw; height:100vh"></div>

    <script src="javascripts/svgPaths.js"></script>
    <script>
        am5.ready(function () {

            var root = am5.Root.new("chartdiv_snsd");
            var chart = root.container.children.push(
                am5percent.SlicedChart.new(root, {})
            );

            var series = chart.series.push(
                am5percent.PictorialStackedSeries.new(root, {
                    svgPath: snsdPath,
                    categoryField: "name",
                    valueField: "value",
                    orientation: "horizontal"
                })
            );

            series.slices.template.setAll({
                tooltipText: "{name} Entertainment: {value} billions KRW."
            });

            series.get("colors").set("colors", [
                am5.color(0xff4980),
                am5.color(0xe9cdc2),
                am5.color(0x773d31),
            ]);

            series.labels.template.set("rotation", 0);
            series.ticks.template.set("location", 0.95);
            series.slices.template.set("tooltipY", 75);

            series.data.setAll([{
                name: "SM",
                value: 365387
            }, {
                name: "JYP",
                value: 102242
            }, {
                name: "YG",
                value: 349861
            }]);
        });
    </script>
</body>

</html>

In this code, we have removed unnecessary script references and kept the essential ones. We also removed the map-related scripts since we are focusing on the SNSD chart in this example.

Next, we need to add a new route in routes/index.js to render the SNSD page:

/* The SNSD page. */
router.get('/snsd', async function (req, res, next) {
  res.render('snsd');
});
/* The SNSD page. */
router.get('/snsd', async function (req, res, next) {
  res.render('snsd');
});

With this route in place, accessing the URL http://localhost:3000/snsd should display the same chart as before.

Provide data from the Route Handler

To provide data from the route handler to the view, you can modify the route handler in routes/index.js as follows:

/* The SNSD page. */
router.get('/snsd', function (req, res, next) {
  res.render('snsd', {
    data: [{
      name: "SM",
      value: 365387
    }, {
      name: "JYP",
      value: 102242
    }, {
      name: "YG",
      value: 349861
    }]
  });
});
/* The SNSD page. */
router.get('/snsd', function (req, res, next) {
  res.render('snsd', {
    data: [{
      name: "SM",
      value: 365387
    }, {
      name: "JYP",
      value: 102242
    }, {
      name: "YG",
      value: 349861
    }]
  });
});

In this code, we pass the data as an object property to the res.render function. The data property contains the same array of objects we used before.

Next, update the snsd.ejs file to use the provided data:

series.data.setAll(eval(`<%- JSON.stringify(data) %>`));
series.data.setAll(eval(`<%- JSON.stringify(data) %>`));

In the JavaScript code block, we update the line series.data.setAll(); to series.data.setAll(eval(<%- JSON.stringify(data) %>));. This change allows us to properly parse and populate the data received from the route handler.

Additionally, we modify the tooltip format to {name} : {value} to display only the name and value in the tooltip.

series.slices.template.setAll({
    tooltipText: "{name} : {value}"
});
series.slices.template.setAll({
    tooltipText: "{name} : {value}"
});

Remember to restart your server and reload the page. You should now see the same chart with the dynamically provided data.

Dynamic data from the Database

To populate the chart with dynamic data from the database, we can modify the route handler in routes/index.js as follows:

/* The SNSD page. */
router.get('/snsd', async function (req, res, next) {

  const db = await connectToDB();
  try {
    let avengers = await db.collection("bookings").find({ team: 'Avengers' }).toArray();
    let jla = await db.collection("bookings").find({ team: 'JLA' }).toArray();
    let neither = await db.collection("bookings").find({ team: '' }).toArray();

    res.render('snsd', {
      data: [
        { name: "Avengers", value: avengers.length },
        { name: "JLA", value: jla.length },
        { name: "Neither", value: neither.length }
      ]
    });

  } catch (err) {
    res.status(400).json({ message: err.message });
  } finally {
    await db.client.close();
  }
});
/* The SNSD page. */
router.get('/snsd', async function (req, res, next) {

  const db = await connectToDB();
  try {
    let avengers = await db.collection("bookings").find({ team: 'Avengers' }).toArray();
    let jla = await db.collection("bookings").find({ team: 'JLA' }).toArray();
    let neither = await db.collection("bookings").find({ team: '' }).toArray();

    res.render('snsd', {
      data: [
        { name: "Avengers", value: avengers.length },
        { name: "JLA", value: jla.length },
        { name: "Neither", value: neither.length }
      ]
    });

  } catch (err) {
    res.status(400).json({ message: err.message });
  } finally {
    await db.client.close();
  }
});

Inside the route handler, we use await with find().toArray() to retrieve the filtered data for each team (Avengers, JLA, and Neither) from the bookings collection. We then store the results in separate variables.

Finally, we render the snsd view and pass the retrieved data as an object property called data. The data property contains an array of objects representing the team names and the corresponding counts.

Now, when you access the /snsd route, the chart will display the dynamic data retrieved from the database, showing the number of documents for each team.

Zip your work and submit to BUmoodle.

20/02/2024 21:28